package ttx.tdd.args;

import cn.hutool.core.map.MapBuilder;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.map.MapWrapper;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import org.assertj.core.util.VisibleForTesting;
import ttx.tdd.args.exception.ArgParseException;
import ttx.tdd.args.parse.*;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @author
 * @date 2022-03-26
 */
public class Args {
    private static final String prefix = "-";
    private static final Map<Class, TypeParser> clazzTypeParserMap = MapBuilder.create(new LinkedHashMap<Class, TypeParser>())
            .put(int.class, new SingleOptionParser<>(Integer::valueOf))
            .put(Integer.class, new SingleOptionParser<>(Integer::valueOf))
            .put(boolean.class, new SingleOptionParser<>(Boolean::valueOf))
            .put(Boolean.class, new SingleOptionParser<>(Boolean::valueOf))
            .put(String.class, new SingleOptionParser<>(String::valueOf))
            .build();

    public static <T> T parse(Class<T> clazz, String argumentAll) {
        if (argumentAll == null) {
            argumentAll = "";
        }
        String[] arguments = argumentAll.split(" ");
        return parse(clazz, arguments);
    }

    public static <T> T parse(Class<T> clazz, String... arguments) {
        try {
            // inputCmd = "-" + cmd
            Map<String, List<String>> inputCmdArgValuesMap = toInputCmdArgValueMap(arguments);
            Map<String, Field> inputCmdFieldMap = toInputCmdFieldMap(clazz);
            return parseOption(clazz, inputCmdArgValuesMap, inputCmdFieldMap);
        } catch (Exception e) {
            if (e instanceof ArgParseException) {
                throw (ArgParseException) e;
            }
            throw new ArgParseException(e);
        }

    }

    private static <T> T parseOption(Class<T> clazz, Map<String, List<String>> inputCmdArgValuesMap, Map<String, Field> inputCmdFieldMap) throws Exception {
        // 设置值
        Constructor<T> con = clazz.getConstructor();
        T optionObj = con.newInstance();
        for (Map.Entry<String, List<String>> inputCmdArgValuesEntry : inputCmdArgValuesMap.entrySet()) {

            String inputCmd = inputCmdArgValuesEntry.getKey();
            List<String> values = inputCmdArgValuesEntry.getValue();
            if (!inputCmdFieldMap.containsKey(inputCmd)) {
                throw new ArgParseException("unkown " + inputCmd);
            }
            Field field = inputCmdFieldMap.get(inputCmd);
            Class fieldClazz = field.getType();
            Object value = clazzTypeParserMap.get(fieldClazz).parse(field.getAnnotation(Option.class), values);
            field.set(optionObj, value);
        }

        return optionObj;
    }

    private static <T> Map<String, Field> toInputCmdFieldMap(Class<T> clazz) {
        Map<String, Field> paramFieldMap = new LinkedHashMap<>();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Option option = field.getAnnotation(Option.class);
            String p = option.cmd();
            String p_ = prefix + p;
            paramFieldMap.put(p_, field);
        }
        return paramFieldMap;
    }

    @VisibleForTesting
    protected static Map<String, List<String>> toInputCmdArgValueMap(String[] arguments) {
        Map<String, List<String>> inputCmdArgValuesMap = new LinkedHashMap<>();
        boolean paramMark = false;
        String inputCmd = null;
        for (int i = 0; i < arguments.length; i++) {
            String arg = arguments[i];
            if (StrUtil.isEmpty(arg)) {
                continue;
            }
            if (!paramMark) {
                if (!arg.startsWith(prefix)) {
                    throw new ArgParseException("should git args start with \"-\"");
                }
                inputCmd = arg;
                inputCmdArgValuesMap.put(inputCmd, new ArrayList<>());
                paramMark = true;
            } else {
                //添加值
                if (arg.startsWith(prefix)) {
                    paramMark = false;
                    inputCmd = null;
                    i--; // 需要重新解析这个命令
                    continue;
                }
                inputCmdArgValuesMap.get(inputCmd).add(arg);
            }
        }
        return inputCmdArgValuesMap;
    }
}
